﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

using SlimDX;

namespace LibSnesDotNet.Core
{
	class Snes : IDisposable
	{
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern uint clr_snes_library_revision_major();
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern uint clr_snes_library_revision_minor();

		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_get_buffer_lengths(ref uint video, ref uint audio);

		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_set_frame_complete(ClrSnesFrameCompleteHandler handler);

		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr clr_snes_enable_synced_input(IntPtr managedAutoEvent);
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_set_input_state(uint port, uint index, ushort buttons, uint coords);
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_set_controller_port_device(bool port, uint device);

		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_init();
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_term();
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_power();
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_reset();
		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_run();

		[DllImport("snes-clr.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void clr_snes_unload_cartridge();

		public static readonly int VersionMajor;
		public static readonly int VersionMinor;
		public static readonly uint MaxVideoBufferLength;
		public static readonly uint MaxAudioBufferLength;

		static Snes()
		{
			VersionMajor = (int)clr_snes_library_revision_major();
			VersionMinor = (int)clr_snes_library_revision_minor();
			clr_snes_get_buffer_lengths(ref MaxVideoBufferLength, ref MaxAudioBufferLength);
		}

		Cartridge _cartridge;
		ClrSnesFrameCompleteHandler _frameCompleteHandler;

		public event VideoUpdatedEventHandler VideoUpdated;
		public event AudioUpdatedEventHandler AudioUpdated;

		public Snes()
		{
			_cartridge = null;

			_frameCompleteHandler = new ClrSnesFrameCompleteHandler(FrameCompleteHandler);
			clr_snes_set_frame_complete(_frameCompleteHandler);

			clr_snes_init();
		}

		public Cartridge Cartridge
		{
			get
			{
				if (_cartridge != null) { _cartridge.Refresh(); }

				return _cartridge;
			}
		}

		public void SetControllerPortDevice(int port, SnesDevice device)
		{
			if (port < 1 || port > 2) { throw new ArgumentOutOfRangeException("port"); }

			clr_snes_set_controller_port_device(port == 2 ? true : false, (uint)device);
		}

		public void RunToFrame()
		{
			if (_cartridge == null) { throw new InvalidOperationException("No rom loaded."); }

			clr_snes_run();
		}

		public void PowerCycle()
		{
			clr_snes_power();
		}

		public void Reset()
		{
			clr_snes_reset();
		}

		public void SetInputState(int port, int index, int buttonStates, int x, int y)
		{
			if (port < 1 || port > 2) { throw new ArgumentOutOfRangeException("port"); }

			SnesJoypadButtons leftRight = SnesJoypadButtons.Left | SnesJoypadButtons.Right;
			if ((buttonStates & (int)leftRight) == (int)(leftRight))
			{
				buttonStates &= (int)~leftRight;
			}

			SnesJoypadButtons upDown = SnesJoypadButtons.Up | SnesJoypadButtons.Down;
			if ((buttonStates & (int)upDown) == (int)upDown)
			{
				buttonStates &= (int)~upDown;
			}

			clr_snes_set_input_state((uint)port - 1, (uint)index, (ushort)buttonStates, (uint)(((ushort)y << 16) | (ushort)x));
		}

		public void LoadCartridge(Cartridge cartridge)
		{
			if (_cartridge != null)
			{
				_cartridge.Refresh();
				clr_snes_unload_cartridge();
			}

			_cartridge = cartridge;
			cartridge.Load(this);
		}

		void FrameCompleteHandler(IntPtr videoBuffer, uint width, uint height, IntPtr audioBuffer, uint count)
		{
			if (VideoUpdated != null)
			{
				VideoUpdated(this, new VideoUpdatedEventArgs(new DataStream(videoBuffer, MaxVideoBufferLength * 2, true, false), (int)width, (int)height));
			}

			if (AudioUpdated != null)
			{
				AudioUpdated(this, new AudioUpdatedEventArgs(new DataStream(audioBuffer, MaxAudioBufferLength * 4, true, false), (int)count));
			}
		}

		public void Dispose()
		{
			Dispose(true);
		}

		public void Dispose(bool disposing)
		{
			if (disposing)
			{
			}

			clr_snes_set_frame_complete(null);
			clr_snes_term();
		}

		~Snes()
		{
			Dispose(false);
		}

		internal delegate void ClrSnesFrameCompleteHandler(IntPtr videoData, uint width, uint height,
		                                                   IntPtr audioData, uint count);
	}
}
